﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Devices;
using Microsoft.Devices.Sensors;
using Microsoft.Xna.Framework.Graphics;
using Matrix = Microsoft.Xna.Framework.Matrix;
using Microsoft.Xna.Framework;
using System.Device.Location;
using System.Diagnostics;
using System.ComponentModel;
using System.Windows.Controls.Primitives;
using Microsoft.Phone.Controls;

namespace ARViewFinder.Controls
{
    public partial class AugmentedViewFinder : UserControl
    {
        PhotoCamera camera;
        Motion motion;

        Viewport viewport;  // "Fenêtre" de vue de la caméra
        Matrix projection;  // Matrice de projection de l'espace 3D sur l'écran
        Matrix view;        // Matrice de position de la caméra

        int WCSRadius = 10; //units
        int Radius = 500; //m

        List<Vector3> points; // Liste des points remarquables dans l'espace
        List<OverlayGroup> AugmentedLabels; // Liste des éléments graphiques à afficher

        AugmentedViewFinderViewModel ViewModel;

        public event AugmentedItemSelectedHandler AugmentedItemSelected;

        public AugmentedViewFinder()
        {
            InitializeComponent();
            points = new List<Vector3>();
            AugmentedLabels = new List<OverlayGroup>();
        }

        public void InitViewport()
        {
            // Le viewport est la "fenêtre" à travers laquelle la caméra regarde, la caméra étant considérée comme un point représentant l'oeil de l'utilisateur.
            viewport = new Viewport(0, 0, (int)this.ActualWidth, (int)this.ActualHeight);
            float aspect = viewport.AspectRatio;

            // La matrice de projection sert à transformer les coordonnées 3D du monde en coordonnées 2D sur l'écran
            projection = Matrix.CreatePerspectiveFieldOfView(1, aspect, 1, 5000);

            // La matrice View représente la caméra, l'oeil de l'utilisateur
            // La caméra pointe dans le sens de l'axe des Z, au centre de l'écran, la direction vers le haut étant l'axe des X
            view = Matrix.CreateLookAt(Vector3.UnitZ, Vector3.Zero, Vector3.UnitX);
        }

        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            if (!DesignerProperties.GetIsInDesignMode(Application.Current.RootVisual))
            { 
                ViewModel = DataContext as AugmentedViewFinderViewModel;

                #region Initialisation de la caméra, et affichage à l'écran
                camera = new PhotoCamera(CameraType.Primary);
                viewfinderBrush.SetSource(camera);
                #endregion

                #region Initialisation de l'API Motion

                if (!Motion.IsSupported)
                {
                    MessageBox.Show("Motion API Not supported :(");
                    return;
                }

                if (motion == null)
                {
                    motion = new Motion();
                    motion.TimeBetweenUpdates = TimeSpan.FromMilliseconds(66); // 15 FPS, largement suffisant
                    motion.CurrentValueChanged += new EventHandler<SensorReadingEventArgs<MotionReading>>(motion_CurrentValueChanged);

                    try
                    {
                        motion.Start();
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show("Impossible de démarrer l'API Motion! " + ex.Message);
                    }
                }
                #endregion

                #region Creation de la liste des "Venues" et calcul de leur position dans l'espace
                foreach (IAugmentable item in ViewModel.Items)
                {
                    // Calcul du cap de l'élément en fonction de la position du téléphone
                    item.Bearing = Math.Round(ARViewFinder.Helpers.ARHelper.CalculateBearing(item.Location, ViewModel.CurrentLocation), 0);
                    item.Distance = ViewModel.CurrentLocation.GetDistanceTo(item.Location);
                }

                var orderedItems  = ViewModel.Items.GroupBy(item => item.Bearing, new IsCloseEnough());

                double BearingMean, DistanceMean;

                foreach (var group in orderedItems)
                {
                    Debug.WriteLine("group");
                    BearingMean = 0;
                    DistanceMean = 0;
                    foreach (var item in group)
                    {
                        Debug.WriteLine("item: " + item.Title + " - bearing: " + item.Bearing);
                        BearingMean += item.Bearing;
                        DistanceMean += item.Distance;
                    }
                    // Calcul de la position de l'élément en fonction de l'angle, et de la distance, dans le repère XNA
                    BearingMean = BearingMean / (double)group.Count();
                    DistanceMean = DistanceMean / (double)group.Count();

                    Debug.WriteLine("BearingMean : " + BearingMean + " - DistanceMean : " + DistanceMean);
                    Vector3 RelativePosition = Helpers.ARHelper.AngleToVector(BearingMean, (WCSRadius * DistanceMean / Radius));
                    AddGroup(RelativePosition, group);
                }
                
                #endregion

            }
        }

        public class IsCloseEnough : IEqualityComparer<double>
        {
            public bool Equals(double x, double y)
            {
 	            if(Math.Abs(x - y) < 20) 
                    return true; 
                else 
                    return false;
            }

            public int  GetHashCode(double obj)
            {
                return 0;
            }
        }

        void motion_CurrentValueChanged(object sender, SensorReadingEventArgs<MotionReading> e)
        {
            Deployment.Current.Dispatcher.BeginInvoke(() =>
            {
                MotionReading motionReading = e.SensorReading;
                if (viewport.Width == 0)
                    InitViewport();

                // Position du device == Matrice de position du Motion Sensor * Rotation à 90% sur l'axe X (le téléphone étant tenu horizontalement)
                Matrix attitude = Matrix.CreateRotationX(MathHelper.PiOver2) * motionReading.Attitude.RotationMatrix;

                for (int i = 0; i < points.Count; i++)
                {
                    // Positionnement de chaque point dans le repère XNA 3D. Z pointe devant, X vers le haut
                    Matrix world = Matrix.CreateWorld(points[i], Vector3.UnitZ, Vector3.UnitX);

                    // Projection du point de l'espace 3D dans le repère 2D de l'écran
                    Vector3 projected = viewport.Project(Vector3.Zero, projection, view, world * attitude);

                    // N'afficher que les Feeds qui sont devant le téléphone.
                    if (projected.Z > 1 || projected.Z < 0)
                    {
                        AugmentedLabels[i].Visibility = System.Windows.Visibility.Collapsed;
                    }
                    else
                    {
                        AugmentedLabels[i].Visibility = System.Windows.Visibility.Visible;

                        // Centrage du contrôle sur le point
                        TranslateTransform tt = new TranslateTransform();
                        tt.X = projected.X - (AugmentedLabels[i].RenderSize.Width / 2);
                        tt.Y = projected.Y - (AugmentedLabels[i].RenderSize.Height / 2);
                        AugmentedLabels[i].RenderTransform = tt;
                    }
                }
            });
        }

        void AddGroup(Vector3 position, IGrouping<double, IAugmentable> group)
        {
            OverlayGroup g = new OverlayGroup();
            g.Items.ItemsSource = group;
            if(AugmentedItemSelected != null)
                g.Tap += new EventHandler<GestureEventArgs>(g_Tap);

            points.Add(position);
            LayoutRoot.Children.Add(g);
            AugmentedLabels.Add(g);
        }

        Popup _navigationPopup;

        void g_Tap(object sender, GestureEventArgs e)
        {
            if(AugmentedItemSelected != null)
            {
                OverlayGroupPopup ogp = new OverlayGroupPopup();
                ogp.DataContext = (sender as OverlayGroup).Items.ItemsSource;
                ogp.lbxPopupItems.SelectionChanged += new SelectionChangedEventHandler(lbxPopupItems_SelectionChanged);

                _navigationPopup = new Popup();
                _navigationPopup.Child = ogp;
                LayoutRoot.Children.Add(_navigationPopup);

                (Application.Current.RootVisual as PhoneApplicationFrame).BackKeyPress += new EventHandler<CancelEventArgs>(AugmentedViewFinder_BackKeyPress);

                _navigationPopup.IsOpen = true;
            }
        }


        void AugmentedViewFinder_BackKeyPress(object sender, CancelEventArgs e)
        {
            if (_navigationPopup != null && _navigationPopup.IsOpen == true)
            {
                _navigationPopup.IsOpen = false;
                _navigationPopup = null;
                e.Cancel = true;
            }
        }

        void lbxPopupItems_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (_navigationPopup != null && (sender as ListBox).SelectedIndex != -1)
            {
                LayoutRoot.Children.Remove(_navigationPopup);
                _navigationPopup.IsOpen = false;
                AugmentedItemSelected(this, new AugmentedItemSelectedEventArgs() { SelectedItem = (sender as ListBox).SelectedItem as IAugmentable });
            }
        }

        public void NavigateToSelectedItem(IAugmentable Item)
        {
            if (this.AugmentedItemSelected != null)
            {
                AugmentedItemSelected(this, new AugmentedItemSelectedEventArgs() { SelectedItem = Item });
            }
        }

    }

    public delegate void AugmentedItemSelectedHandler (object sender, AugmentedItemSelectedEventArgs e);
    public class AugmentedItemSelectedEventArgs : EventArgs
    {
        public IAugmentable SelectedItem;
    }
}
